In [8]:
t = 'a', 'b', 'c', 'd', 'e'
Out[8]:
Although it is not necessary, it is common to enclose tuples in parentheses:
In [9]:
t = ('a', 'b', 'c', 'd', 'e')
Out[9]:
To create a tuple with a single element, you have to include a final comma:
In [10]:
t = ('a',)
typeof(t)
Out[10]:
A value in parentheses is not a tuple:
In [11]:
t = ('a')
typeof(t)
Out[11]:
Another way to create a tuple is the built-in function tuple
. With no argument, it creates an empty tuple:
In [16]:
t = tuple()
Out[16]:
If multiple arguments are provided, the result is a tuple with the given arguments:
In [18]:
t = tuple(1, 'a', pi)
Out[18]:
Most array operators also work on tuples. The bracket operator indexes an element:
In [27]:
t = ('a', 'b', 'c', 'd', 'e')
t[1]
Out[27]:
And the slice operator selects a range of elements:
In [20]:
t[2:3]
Out[20]:
But if you try to modify one of the elements of the tuple, you get an error:
In [21]:
t[1] = 'A'
Because tuples are immutable, you can’t modify the elements.
The relational operators work with tuples and other sequences; Julia starts by comparing the first element from each sequence. If they are equal, it goes on to the next elements, and so on, until it finds elements that differ. Subsequent elements are not considered (even if they are really big).
In [29]:
(0, 1, 2000000) < (0, 3, 4)
Out[29]:
It is often useful to swap the values of two variables. With conventional assignments, you have to use a temporary variable. For example, to swap a and b:
temp = a
a = b
b = temp
This solution is cumbersome; tuple assignment is more elegant:
a, b = b, a
The left side is a tuple of variables; the right side is a tuple of expressions. Each value is assigned to its respective variable. All the expressions on the right side are evaluated before any of the assignments.
Strictly speaking, a function can only return one value, but if the value is a tuple, the effect is the same as returning multiple values. For example, if you want to divide two integers and compute the quotient and remainder, it is inefficient to compute x÷y
and then x%y
. It is better to compute them both at the same time.
The built-in function divrem
takes two arguments and returns a tuple of two values, the quotient and remainder. You can store the result as a tuple:
In [44]:
t = divrem(7, 3)
Out[44]:
Or use tuple assignment to store the elements separately:
In [46]:
quot, rem = divrem(7, 3)
println(quot)
println(rem)
Here is an example of a function that returns a tuple:
In [1]:
function min_max(t)
minimum(t), maximum(t)
end
Out[1]:
max
and min
are built-in functions that find the largest and smallest elements of a sequence. min_max
computes both and returns a tuple of two values.
In [47]:
function printall(args...)
println(args)
end
Out[47]:
The gather parameter can have any name you like, but args is conventional. Here’s how the function works:
In [48]:
printall(1, 2.0, '3')
The complement of gather is scatter. If you have a sequence of values and you want to pass it to a function as multiple arguments, you can use the ...
operator. For example, divrem
takes exactly two arguments; it doesn’t work with a tuple:
In [49]:
t = (7, 3)
divrem(t)
But if you scatter the tuple, it works:
In [50]:
divrem(t...)
Out[50]:
Many of the built-in functions use variable-length argument tuples. For example, max
and min
can take any number of arguments:
In [51]:
max(1, 2, 3)
Out[51]:
But sum
does not:
In [52]:
sum(1, 2, 3)
In [72]:
s = "abc"
t = [1, 2, 3]
zip(s, t)
Out[72]:
The result is a Zip2
object that knows how to iterate through the pairs. The most common use of zip
is in a for
loop:
In [54]:
for pair in zip(s, t)
println(pair)
end
A zip object is a kind of iterator, which is any object that iterates through a sequence. Iterators are similar to arrays in some ways, but unlike arrays, you can’t use an index to select an element from an iterator.
If you want to use array operators and functions, you can use a zip object to make an array:
In [55]:
collect(zip(s, t))
Out[55]:
The result is an array of tuples; in this example, each tuple contains a character from the string and the corresponding element from the array.
If the sequences are not the same length, the result has the length of the shorter one.
In [56]:
collect(zip("Anne", "Elk"))
Out[56]:
You can use tuple assignment in a for
loop to traverse an array of tuples:
In [71]:
t = [('a', 1), ('b', 2), ('c', 3)]
for (letter, number) in t
println(number, " ", letter)
end
Each time through the loop, Julia selects the next tuple in the array and assigns the elements to letter and number. The parentheses around letter, number)
are compulsory.
If you combine zip
, for
and tuple assignment, you get a useful idiom for traversing two (or more) sequences at the same time. For example, has_match takes two sequences, t1
and t2
, and returns true
if there is an index i
such that t1[i] == t2[i]
:
In [62]:
function has_match(t1, t2)
for (x, y) in zip(t1, t2)
if x == y
return true
end
end
false
end
has_match("Hello", "World")
Out[62]:
If you need to traverse the elements of a sequence and their indices, you can use the built-in function enumerate
:
In [63]:
for (index, element) in enumerate("abc")
println(index, " ", element)
end
The result from enumerate
is an enumerate object, which iterates a sequence of pairs; each pair contains an index (starting from 1) and an element from the given sequence.
In [70]:
d = Dict('a'=>1, 'b'=>2, 'c'=>3)
for (key, value) in d
println(key, " ", value)
end
As you should expect from a dictionary, the items are in no particular order.
Going in the other direction, you can use an array of tuples to initialize a new dictionary:
In [69]:
t = [('a', 1), ('c', 3), ('b', 2)]
d = Dict(t)
Out[69]:
Combining Dict
with zip
yields a concise way to create a dictionary:
In [68]:
d = Dict(zip("abc", 1:3))
Out[68]:
It is common to use tuples as keys in dictionaries. For example, a telephone directory might map from last-name, first-name pairs to telephone numbers. Assuming that we have defined last, first and number, we could write:
In [85]:
last = "Caesar"
first = "Julius"
number = "0032020000000"
directory = Dict()
directory[last, first] = number
directory
Out[85]:
The expression in brackets is a tuple. We could use tuple assignment to traverse this dictionary.
In [78]:
for ((last, first), number) in directory
println(first, " ", last, " ", number)
end
This loop traverses the key-value pairs in directory, which are tuples. It assigns the elements of the key in each tuple to last and first, and the value to number, then prints the name and corresponding telephone number.
There are two ways to represent tuples in a state diagram. The more detailed version shows the indices and elements just as they appear in an array.
In [84]:
using TikzPictures
TikzPicture(L"""
\node(hist) [draw, fill=lightgray, minimum width=2.5cm, minimum height=1cm]{};
\node(nc) at(-1,0.25) {1};
\node(c) at(0.5,0.25) {"Cleese"};
\draw[-latex](nc)--(c);
\node(nj) at(-1,-0.25) {2};
\node(j) at(0.5,-0.25) {"John"};
\draw[-latex](nj)--(j);
"""; options="very thick, scale=2, transform shape", preamble="""
\\usepackage{newtxmath}
\\renewcommand{\\familydefault}{\\sfdefault}
\\usepackage{cancel}
""")
Out[84]:
But in a larger diagram you might want to leave out the details.
In [101]:
using TikzPictures
TikzPicture(L"""
\node(hist) [draw, fill=lightgray, minimum width=8cm, minimum height=3cm]{};
\node(nc) at(-2,1.25) {("Cleese","John")};
\node(c) at(2,1.25) {0032020000000};
\draw[-latex](nc)--(c);
\node(ng) at(-2,0.75) {("Chapman","Graham")};
\node(g) at(2,0.75) {0032020000001};
\draw[-latex](ng)--(g);
\node(ni) at(-2,0.25) {("Idle","Eric")};
\node(i) at(2,0.25) {0032020000002};
\draw[-latex](ni)--(i);
\node(nt) at(-2,-0.25) {("Gilliam","Terry")};
\node(t) at(2,-0.25) {0032020000003};
\draw[-latex](nt)--(t);
\node(nj) at(-2,-0.75) {("Jones","Terry")};
\node(j) at(2,-0.75) {0032020000004};
\draw[-latex](nj)--(j);
\node(np) at(-2,-1.25) {("Palin","Michael")};
\node(p) at(2,-1.25) {0032020000005};
\draw[-latex](np)--(p);
"""; options="very thick, scale=2, transform shape", preamble="""
\\usepackage{newtxmath}
\\renewcommand{\\familydefault}{\\sfdefault}
\\usepackage{cancel}
""")
Out[101]:
Here the tuples are shown using Julia syntax as a graphical shorthand.
I have focused on arrays of tuples, but almost all of the examples in this chapter also work with arrays of arrays, tuples of tuples, and tuples of arrays. To avoid enumerating the possible combinations, it is sometimes easier to talk about sequences of sequences.
In many contexts, the different kinds of sequences (strings, arrays and tuples) can be used interchangeably. So how should you choose one over the others?
To start with the obvious, strings are more limited than other sequences because the elements have to be characters. They are also immutable. If you need the ability to change the characters in a string (as opposed to creating a new string), you might want to use an array of characters instead.
Arrays are more common than tuples, mostly because they are mutable. But there are a few cases where you might prefer tuples:
In some contexts, like a return statement, it is syntactically simpler to create a tuple than an array.
If you want to use a sequence as a dictionary key, you have to use an immutable type like a Tuple
or String
.
If you are passing a sequence as an argument to a function, using tuples reduces the potential for unexpected behavior due to aliasing.
Because tuples are immutable, they don’t provide function like sort!
and reverse!
, which modify existing arrays. But Julia provides the built-in function sort
, which takes an array and returns a new array with the same elements in sorted order, and reverse
, which takes any sequence and returns a sequence of the same type in reverse order.
Arrays, dictionaries and tuples are examples of data structures; in this lecture we are starting to see compound data structures, like arrays of tuples, or dictionaries that contain tuples as keys and arrays as values. Compound data structures are useful, but they are prone to what I call shape errors; that is, errors caused when a data structure has the wrong type, size, or structure. For example, if you are expecting an array with one integer and I give you a plain old integer (not in an array), it won’t work.